Skip to main content

The garbage collector

Flow-Wing manages heap memory for you. There is no free, no ownership, no lifetimes. This page explains what runs underneath.

Interactive version

Everything below has a step-through visualisation — watch marking propagate through a live heap graph, toggle the flag bits inside an object header, and compare descriptor kinds.

▶ Open the interactive GC walkthrough

The short version​

PropertyWhat it means
PreciseThe collector knows exactly which words are pointers, from a per-type descriptor. It never guesses from bit patterns.
Non-movingA live object never changes address, so no read or write barriers are needed.
Mark and sweepFind everything reachable, free the rest. Two phases, stop-the-world.
Single-threadedNo locks anywhere. This is precisely why spawn uses coroutines instead of threads.

When a collection happens​

Allocation is the only trigger — there is no background thread:

if (g_stress || g_stats.live_bytes + size > g_threshold) {
fw_gc_collect();
}

After each collection the threshold becomes live_bytes * 2 (floor 1 MiB). A fixed threshold would make an all-live workload collect on every allocation, walking the whole heap each time — O(N²). Doubling gives O(log N) collections.

Testing your program under pressure​

FW_GC_STRESS=1 ./myprogram

That collects on every single allocation. If the output is identical to a normal run, every value your program holds is properly rooted. If it changes, something was reachable only by luck.

Every fixture under tests/fixtures/LatestTests/GcTests/ runs with stress on automatically. A fixture elsewhere opts in with a /; FW_GC_STRESS header line.

Tasks and the collector​

A suspended task's variables live on that task's own stack, which the normal root chain does not point at. A queued task's arguments are referenced by nothing at all. The scheduler registers an auxiliary root scanner so the collector reaches both:

fw_gc_set_aux_root_scanner(fn); /* scheduler registers itself */
fw_gc_mark_shadow_chain(top); /* one parked task's frames */
fw_gc_push_root_object(obj); /* a queued task's argument block */

See Concurrency and the Event Loop for the other side of that handshake.

Where the code lives​

FileContains
fw-modules/gc/src/fw_gc_core.callocation, roots, finalizers, the object index
fw-modules/gc/src/fw_gc_mark.cwork-list, scan_fields, root seeding
fw-modules/gc/src/fw_gc_sweep.csweep, poisoning, threshold growth
fw-modules/gc/src/fw_gc_header.cpacking the descriptor word and its flag bits
fw-modules/gc/tests/test_gc.c982 checks — run with make test-gc